package com.significantfiles.snapshot.engine.compiler;

import java.util.HashMap;
import java.util.Map;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.Descriptor;
import javassist.bytecode.MethodInfo;

import com.significantfiles.snapshot.engine.compiler.model.x.EntityInterfaceIsBrokenException;
import com.significantfiles.snapshot.engine.util.Utils;

public class EntityInterfaceParser {

	private static final boolean HANDLE_METHOD = false;
	private static final boolean SKIP_METHOD = true;

	private final Map<Class<?>, Class<?>> generators = new HashMap<Class<?>, Class<?>>();

	public EntityInterfaceParser() {
		;
	}

	public void parse(final Class<?> modelInterface)
			throws EntityInterfaceIsBrokenException {
		final ClassPool classPool = ClassPool.getDefault();

		try {
			final CtClass modelDef = classPool.get(modelInterface.getName());

			final boolean isInterface = modelDef.isInterface();

			if (!isInterface) {
				throw new EntityInterfaceIsBrokenException();
			}

			final EntityClassGenerator generator = new EntityClassGenerator(
					modelDef);

			parseMethods(modelDef, generator);

			final Class<?> impl = generator.createClass();
			this.generators.put(modelInterface, impl);
		} catch (final NotFoundException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private static void parseMethods(final CtClass modelDef,
			final EntityClassGenerator generator) throws NotFoundException,
			EntityInterfaceIsBrokenException {
		final CtMethod[] methods = modelDef.getMethods();

		for (int i = 0; i < methods.length; i++) {
			final CtMethod ctMethod = methods[i];
			parseMethod(ctMethod, modelDef, generator);
		}
	}

	private static void parseMethod(final CtMethod ctMethod,
			final CtClass modelDef, final EntityClassGenerator generator)
			throws NotFoundException, EntityInterfaceIsBrokenException {
		final boolean isNotGetterMethod = shouldSkipModelMethod(ctMethod);

		if (isNotGetterMethod)
			return;

		checkSetter(ctMethod, modelDef);

		final EntityFieldGenerator fieldGenerator = new EntityFieldGenerator(
				ctMethod);

		generator.addFieldGenerator(fieldGenerator);
	}

	private static final CtClass getGetterReturnType(final CtMethod getter)
			throws EntityInterfaceIsBrokenException {
		try {
			return getter.getReturnType();
		} catch (final NotFoundException e) {
			throw new EntityInterfaceIsBrokenException();
		}
	}

	private static void checkSetter(final CtMethod getter, final CtClass owner)
			throws EntityInterfaceIsBrokenException {

		final CtClass returnType = getGetterReturnType(getter);

		final String setterName = calcSetterName(getter);

		final String setterMethodSignature = calcSetterSignature(returnType);

		try {
			owner.getMethod(setterName, setterMethodSignature);
		} catch (final NotFoundException e) {
			final String logMsg = "Couldn't parse this interface [name: "
					+ owner.getName()
					+ "], because no setter exists for a founded getter-method [name : "
					+ getter.getName() + "].";
			throw new EntityInterfaceIsBrokenException(logMsg, e);
		}
	}

	private static boolean isModifiedAtGetter(final CtMethod getter) {
		final String name = getter.getName();
		return "getModifiedAt".equals(name);
	}

	private static boolean isIdGetter(final CtMethod getter) {
		final String name = getter.getName();
		return "getCreatedAt".equals(name);
	}

	private static boolean isCreatedAtGetter(final CtMethod getter) {
		final String name = getter.getName();
		return "getId".equals(name);
	}

	private static String calcSetterName(final CtMethod ctMethod) {
		final String setterName = "set" + ctMethod.getName().substring(3);
		return setterName;
	}

	private static String calcSetterSignature(final CtClass getterReturnType) {
		final String setterParamSignature = Descriptor
				.ofParameters(new CtClass[] { getterReturnType });
		return setterParamSignature + "V";
	}


	private static boolean shouldSkipModelMethod(final CtMethod ctMethod)
			throws NotFoundException {
		if (isCreatedAtGetter(ctMethod))
			return SKIP_METHOD;
		
		if (isIdGetter(ctMethod))
			return SKIP_METHOD;
		
		if (isModifiedAtGetter(ctMethod))
			return SKIP_METHOD;

		final MethodInfo methodInfo = ctMethod.getMethodInfo();

		if (!methodInfo.isMethod())
			return SKIP_METHOD;

		final String methodName = methodInfo.getName();

		final boolean isGetterName = Utils.isGetterName(methodName);

		if (!isGetterName) {
			// TODO: exception
			return SKIP_METHOD;
		}

		if ("getClass".equals(methodName)) {
			return SKIP_METHOD;
		}

		final CtClass[] parameterTypes = ctMethod.getParameterTypes();

		if (parameterTypes.length > 0) {
			// TODO: Exception
			return SKIP_METHOD;
		}

		final CtClass returnType = ctMethod.getReturnType();

		final String rvalTypeName = returnType.getName();

		if ("void".equals(rvalTypeName)) {
			// todo Exception
			return SKIP_METHOD;
		}

		return HANDLE_METHOD;
	}

	// TODO: chesk, parsen wenn noetig, synchronized
	@SuppressWarnings( { "unchecked" })
	public <T> Class<T> get(final Class<T> modelItf) {
		if (modelItf == null)
			throw new NullPointerException();

		final Class<?> modelImpl = this.generators.get(modelItf);

		if (modelImpl == null)
			throw new IllegalStateException();

		return (Class<T>) modelImpl;
	}

}
